﻿/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Console
{

	public abstract class Variable
	{
		internal Variable( string name )
		{
			if( name == null )
				throw new ArgumentNullException( "name" );
			if( name == string.Empty )
				throw new ArgumentException();

			this.name = name;
		}

		private string name;
		/// <summary>
		/// Gets the variable's name.
		/// </summary>
		public string Name
		{
			get { return name; }
		}

		private string description = string.Empty;
		/// <summary>
		/// Gets or sets a description of the variable.
		/// </summary>
		public string Description { get { return description; } set { description = value ?? string.Empty; } }

		/// <summary>
		/// Raised whenever the variable's value is changed.
		/// </summary>
		public event EventHandler ValueChanged;

		protected virtual void OnValueChanged( EventArgs e )
		{
			if( ValueChanged != null )
				ValueChanged( this, e );
		}

		protected internal abstract void Reset();

		internal void RaiseChanged()
		{
			OnValueChanged( EventArgs.Empty );
		}
	}

	public sealed class Variable<T> : Variable
	{

		/// <summary>
		/// Creates a new variable which may be registered with the console.
		/// </summary>
		/// <param name="name">The name of the variable to create.</param>
		/// <param name="metadata">The variable's metadata.</param>
		public Variable( string name, VariableMetadata<T> metadata )
			: base( name )
		{
			if( metadata == null )
				throw new ArgumentNullException( "metadata" );

			this.metadata = metadata;
			this.value = metadata.DefaultValue;
		}

		/// <summary>
		/// Creates a new variable which may be registered with the console.
		/// </summary>
		/// <param name="name">The name of the variable to create.</param>
		public Variable( string name )
			: this( name, VariableMetadata<T>.Default )
		{
		}

		private T value;
		/// <summary>
		/// Gets or sets the variable's current value.
		/// </summary>
		public T Value
		{
			get { return value; }
			set
			{
				if( !metadata.ValidateValueChange( this.value, value ) )
					return;

				this.value = value;

				OnValueChanged( EventArgs.Empty );
			}
		}

		private VariableMetadata<T> metadata;
		/// <summary>
		/// Gets the metadata associated with this variable.
		/// </summary>
		public VariableMetadata<T> Metadata { get { return metadata; } }

		protected internal override void Reset()
		{
			this.value = metadata.DefaultValue;
			OnValueChanged( EventArgs.Empty );
		}
	}

	public sealed class VariableMetadata<T>
	{
		private T defaultValue;
		/// <summary>
		/// Gets the default value associated with this metadata.
		/// </summary>
		public T DefaultValue { get { return defaultValue; } }

		/// <summary>
		/// A validator function to check variable value changes.
		/// </summary>
		/// <param name="oldValue">The variable's current value.</param>
		/// <param name="newValue">The proposed new value.</param>
		/// <returns>
		/// <c>true</c> to accept the change, <c>false</c> otherwise.
		/// </returns>
		public delegate bool ChangeValidatorFunc( T oldValue, T newValue );

		private ChangeValidatorFunc validateFunc;

		internal bool ValidateValueChange( T oldValue, T newValue )
		{
			if( validateFunc != null )
				return validateFunc( oldValue, newValue );

			return true;
		}

		private VariableMetadata()
		{
		}

		public static VariableMetadata<T> CreateSimple( T defaultValue )
		{
			var ret = new VariableMetadata<T>();

			ret.defaultValue = defaultValue;

			return ret;

		}

		private static VariableMetadata<T> defaultMetadata;
		/// <summary>
		/// Gets a default metadata, who's default value is the
		/// type default and which accepts all changes.
		/// </summary>
		public static VariableMetadata<T> Default
		{
			get
			{
				if( defaultMetadata == null )
					//doesn't matter if a thread race happens here,
					//the value would be stomped with an identical object
					defaultMetadata = new VariableMetadata<T>();

				return defaultMetadata;
			}
		}

		/// <summary>
		/// A simple class to build metadata values.
		/// </summary>
		public struct Builder
		{
			private VariableMetadata<T> lastMeta;

			private T defaultValue;
			private ChangeValidatorFunc changeValidator;

			/// <summary>
			/// Gets or sets the default value to associate
			/// with any created metadata.
			/// </summary>
			public T DefaultValue
			{
				get { return defaultValue; }
				set
				{
					defaultValue = value;
					lastMeta = null;
				}
			}
			/// <summary>
			/// Gets or sets a function to validate changes
			/// to the value of the variable.
			/// </summary>
			public ChangeValidatorFunc ChangeValidator
			{
				get { return changeValidator; }
				set
				{
					changeValidator = value;
					lastMeta = null;
				}
			}

			/// <summary>
			/// Captures the current <c>Builder</c> properties
			/// into a new <see cref="VariableMetadata<T>"/>.
			/// </summary>
			/// <returns>
			/// A newly constructed <c>VariableMetadata&lt;T&gt;</c>
			/// initialized with the current <c>Builder</c> properties.
			/// </returns>
			public VariableMetadata<T> CreateMetadata()
			{
				if( lastMeta != null )
					return lastMeta;

				var ret = new VariableMetadata<T>();

				ret.defaultValue = DefaultValue;
				ret.validateFunc = ChangeValidator;

				lastMeta = ret;

				return ret;
			}
		}
	}

}